#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>

#include "magma_new.h"
#include "cmac.h"


/*                                                                     
   dd99 99                                                                                                      
  d9"   99                                                                                 
  99    99                                                           
NN99NNN 99 ,ddEE22ed, 99,dEDed,,ddE2ed,   
  99    99 ""     `D9 99E'   "99"    "9d 
  99    99 ,ddEEEEE99 99      99      99  
  99    99 99,    ,99 99      99      99 
  99    99 `"9eedE"29 99      99      99             

            .-.
           ((`-)
            \\
             \\
      .="""=._))
     /  .,   .'
    /__(,_.-'
   `    /|
       /_|__
         | `))
         |
        -"==

*/

char help_mess[] = "SYNOPSIS\n"
                   "\tmagma-cmac [OPTIONS] [PATH]\n\n"
                   "OPTIONS\n"
                   "\t-v [0|1|2] - The amount of information displayed on the screen.\n"
                   "\t\t0 - (by default) Displays only information about the transferred file\n"
                   "\t\t1 - If it is a directory, it outputs information about all files in the directory (without recursion)\n"
                   "\t\t2 - If it is a directory, it outputs information about all files in the directory (recursion)\n"
                   "\t-k [KEY] - The key used for the encryption algorithm\n"
                   "\t-p - Path to the directory\n"
                   "\t-h - Output help message\n"
                   "PATH\n"
                   "\tThe path to the file from which the checksum is being removed\n";

uint64_t K1, K2;

#define STRCMP(str1, str2) (strcmp(str1, str2) == 0)? 1: 0

void ReadKey(uint32_t *key, char *input);
uint8_t ReadFile(int fd, uint64_t *P);
void EncodeDir(char *path, uint64_t *MAC_DIR, int level_verb, int s);
void EncodeFile(char *path, uint64_t *MAC, uint8_t s);


void ReadKey(uint32_t *key, char *input){
    char byte[8];
    for (int i = 0; i < 8; ++i){
        errno = 0;
        memcpy(byte, input+i*8, 8);
        key[i] = (uint32_t)strtol(byte, NULL, 16);

        if (errno != 0){
            perror("Invalid key\n");
            exit(EXIT_FAILURE);
        }
    }
}


uint8_t ReadFile(int fd, uint64_t *P){
    uint8_t buffer[8];
    uint8_t number_read_byte = 0;
    uint8_t k = 0;

    while ((number_read_byte = read(fd, buffer, 8)) > 0){
        for (int i = 0; i < number_read_byte; ++i){
            P[k] |= buffer[i];
            P[k] <<= 8;
        }
        k++;
        if (k == 64)
            return 64;
    }
    
    return k;
}


void PrintShift(uint8_t shift){
    for (int i = 0; i < shift; ++i)
        printf("\t");
}


void EncodeDir(char *path, uint64_t *HASH, int level_verb, int s){
    static uint8_t shift = 0;
    DIR *d;
    struct dirent *dir;
    struct stat sb;
    int fd;
    uint64_t MAC_DIR = 0;
    uint64_t MAC = 0;
    uint64_t Cq_1 = 0;
    uint8_t ind = 0;
    uint8_t len_file = 0;
    char full_path_to_root[513] = {0};
    uint64_t P[64] = {0};

    strcat(full_path_to_root, path);
    strcat(full_path_to_root, "/");
    d = opendir(path);

    if (d == NULL){
        perror("opendir");
        exit(EXIT_FAILURE);
    }

    if (level_verb != 0 || shift == 0){
        if (level_verb == 0){
            PrintShift(shift);
            printf("<<<<<<<<%s>>>>>>>>\n", path);
        }
        if (level_verb == 2 && shift < 2){
            PrintShift(shift);
            printf("<<<<<<<<%s>>>>>>>>\n", path);
        }
        if (level_verb == 3){
            PrintShift(shift);
            printf("<<<<<<<<%s>>>>>>>>\n", path);
        }
    }
    shift++;

    while ((dir = readdir(d)) != NULL){
        if (STRCMP(dir->d_name, ".") || STRCMP(dir->d_name, ".."))
            continue;

//        printf("%s\n", path);
        strcat(full_path_to_root, dir->d_name);
//        printf("%s\n", full_path_to_root);
        len_file = strlen(dir->d_name);

        if (lstat(full_path_to_root, &sb) == -1){
            perror("lstat");
            exit(EXIT_FAILURE);
        }

        if(S_ISDIR(sb.st_mode)){
            EncodeDir(full_path_to_root, &MAC, level_verb, s);
            P[ind] = MAC;
            ind++;
        }
        else{
            EncodeFile(full_path_to_root, &MAC, s);
            P[ind] = MAC;
            ind++;
            if (level_verb != 0){
                if (level_verb == 0){
                    PrintShift(shift);
                    printf("%s: 0x%016llX\n", dir->d_name, MAC);
                }
                if (level_verb == 2 && shift < 2){
                    PrintShift(shift);
                    printf("%s: 0x%016llX\n", dir->d_name, MAC);
                }
                if (level_verb == 3){
                    PrintShift(shift);
                    printf("%s: 0x%016llX\n", dir->d_name, MAC);
                }
            }
        }

        if (ind == 64){
            CMAC_Iter(ind, P, &MAC_DIR, &Cq_1);
            ind = 0;
        }

        full_path_to_root[strlen(full_path_to_root) - len_file] = '\0';
    }

    if (ind != 0)
        CMAC_Iter(ind, P, &MAC_DIR, &Cq_1);

    if (P[ind-1] &f_bit)
        CMAC_ReturnHash(ind-1, P, &MAC_DIR, &Cq_1, &K1, s);
    else
        CMAC_ReturnHash(ind-1, P, &MAC_DIR, &Cq_1, &K2, s);

    *HASH = MAC_DIR;
    if (level_verb != 0 || shift == 1){
        if (level_verb == 0){
            PrintShift(shift);
            printf("[%s]: 0x%016llX\n", path, MAC_DIR);
        }
        if (level_verb == 2 && shift < 3){
            PrintShift(shift);
            printf("[%s]: 0x%016llX\n", path, MAC_DIR);
        }
        if (level_verb == 3){
            PrintShift(shift);
            printf("[%s]: 0x%016llX\n", path, MAC_DIR);
        }
    }

    shift--;

    if (level_verb != 0 || shift == 0){
        if (level_verb == 0){
            PrintShift(shift);
            printf(">>>>>>>>%s<<<<<<<<\n", path);      
        }
        if (level_verb == 2 && shift < 2){
            PrintShift(shift);
            printf(">>>>>>>>%s<<<<<<<<\n", path);
        }
        if (level_verb == 3){
            PrintShift(shift);
            printf(">>>>>>>>%s<<<<<<<<\n", path);
        }
    }

    closedir(d);
}


void EncodeFile(char *path, uint64_t *MAC, uint8_t s){
    int fd;
    int number_blk = 0;
    uint64_t P[64] = {0};
    uint64_t Cq_1 = 0;
    int ind = 0;

    fd = open(path, O_RDONLY);

    while((number_blk = ReadFile(fd, P)) != 0){
        CMAC_Iter(number_blk, P, MAC, &Cq_1);
        ind = number_blk-1;
    }

    if (P[ind] & f_bit)
        CMAC_ReturnHash(ind, P, MAC, &Cq_1, &K1, s);
    else
        CMAC_ReturnHash(ind, P, MAC, &Cq_1, &K2, s);

    close(fd);
}


int main(int argc, char *argv[]){
    uint32_t Key[8];
    char input[65];
    uint64_t mess = 0;
    uint64_t R = 0;
    uint64_t P[64] = {0};
    uint64_t MAC = 0;
    int fd;
    char path[513] = {0};
    uint8_t number_blk = 0;
    uint8_t s = 64;
    uint64_t Cq_1;
    uint8_t ind;
    uint8_t level_verbose = 0;

    struct stat sb;

    if (argc == 1){
        printf("%s", help_mess);
        exit(EXIT_SUCCESS);
    }

    for (int i = 1; i < argc; ++i){
        if (STRCMP(argv[i], "-h")){
            printf("%s", help_mess);
            exit(EXIT_SUCCESS);
        }
        else if (STRCMP(argv[i], "-v") && (i + 1 <= argc-1)){
            char buff[2];
            strncpy(buff, argv[i+1], 1);
            level_verbose = atoi(buff);

            if (level_verbose < 0 && level_verbose > 2){
                printf("Incorrect level of verbose\n");
                exit(EXIT_FAILURE);
            }
            i++;
        }
        else if (STRCMP(argv[i], "-k") && (i+1 <= argc - 1)){
            if (strlen(argv[i+1]) != 64){
                printf("The key must be 8 bytes\n");
                exit(EXIT_FAILURE);
            }
            strncpy(input, argv[i+1], 64);
            i++;
        }
        else if (STRCMP(argv[i], "-s") && (i+1 <= argc - 1)){
            uint8_t tmp = atoi(argv[i+1]);
            s = tmp;
            i++;
        }
        else if (STRCMP(argv[i], "-p") && (i+1 <= argc - 1)){
            strncpy(path, argv[i+1], 512);
            i++;
        }
        else{
            printf("Incorrect options\n");
            exit(EXIT_FAILURE);
        }
    }

    if (strlen(path) == 0){
        printf("Path not entered\n");
        exit(EXIT_FAILURE);
    }

    ReadKey(Key, input);
    Magma_KeyDeployment(Key);
    Magma_Encript(&mess, &R);
    CMAC_GenerateSecKeys(&R, &K1, &K2);
    
    // scanf("%s", path);
    if (lstat(path, &sb) == -1){
        perror("lstat");
        exit(EXIT_FAILURE);
    }

    if (S_ISDIR(sb.st_mode)){
        EncodeDir(path, &MAC, level_verbose, s);
    }
    else{
        EncodeFile(path, &MAC, s);
        printf("[%s]: 0x%016llX\n", path, MAC);
    }
}
